using System;
using System.Diagnostics;
using Needle.Core.Injection;
using Needle.Utils;

namespace Needle.Core
{

    /// <summary>
    /// Identifies a dependency by type and name.
    /// </summary>
    [Serializable]
    [DebuggerDisplay("InstanceId {ToString()}")]
    public class InstanceId : IEquatable<InstanceId>
    {
        private const string SEPARATOR = ":";
        private const string EMPTY_ID = "<Empty>";

        #region fields

        readonly int hashcode;

        #endregion

        #region constructors

        private InstanceId()
        {
            hashcode = ToString().GetHashCode();
        }

        /// <summary>
        /// Initializes a new instance
        /// </summary>
        public InstanceId(Type t)
            : this(t, null)
        {
        }

        /// <summary>
        /// Initializes a new instance
        /// </summary>
        public InstanceId(Type t, string name)
        {
            Ensure.NotNull(t, "t");
            m_Type = t;
            m_Name = name;
            hashcode = ToString().GetHashCode();
        }

        #endregion

        #region properties

        private readonly Type m_Type;
        /// <summary>
        /// The type of the dependency
        /// </summary>
        public Type Type
        {
            get { return m_Type; }
        }

        private readonly string m_Name;
        /// <summary>
        /// The name of the dependency, or null
        /// </summary>
        public string Name
        {
            get { return m_Name; }
        }
        /// <summary>
        /// returns true if this dependency has a name.
        /// </summary>
        public bool IsNamed
        {
            get { return !String.IsNullOrEmpty(m_Name); }
        }
        /// <summary>
        /// returns true if this is the null dependency.
        /// </summary>
        public bool IsNull
        {
            get { return m_Type == null; }
        }

        #endregion

        #region ToString
        /// <summary>
        /// Returns a string representation.
        /// </summary>
        public override string ToString()
        {
            if (m_Type == null)
            {
                return EMPTY_ID;
            }
            if (IsNamed)
            {
                return m_Name + SEPARATOR + m_Type.AssemblyQualifiedName;
            }
            else
            {
                return m_Type.AssemblyQualifiedName;
            }
        }

        #endregion

        #region Equals
        /// <summary>
        /// Returns the hashcode.
        /// </summary>
        public override int GetHashCode()
        {
            return hashcode;
        }
        /// <summary>
        /// Determines is this instance is equal to the object given.
        /// </summary>
        public override bool Equals(object obj)
        {
            if (obj is InstanceId)
            {
                return ((IEquatable<InstanceId>)this).Equals((InstanceId)obj);
            }
            return false;
        }

        /// <summary>
        /// Determines is this instance is equal to the other id.
        /// </summary>
        public bool Equals(InstanceId other)
        {
            if (Object.Equals(other, null))
                return false;
            return this.m_Type == other.m_Type
                 && this.m_Name == other.m_Name;
        }

        #endregion

        #region operators

        /// <summary>
        /// Equality operator overload.
        /// </summary>
        public static bool operator ==(InstanceId a, InstanceId b)
        {
            if (Object.Equals(a, null))
                return Object.Equals(b, null);
            if (Object.Equals(b, null))
                return false;
            return a.Equals(b);
        }
        /// <summary>
        /// Inequality operator overload
        /// </summary>
        public static bool operator !=(InstanceId a, InstanceId b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Explicit cast from string to DependencyId.
        /// </summary>
        public static explicit operator InstanceId(String instanceId)
        {
            try
            {
                if (instanceId == null)
                    return null;
                return InstanceId.Parse(instanceId);
            }
            catch (FormatException ex)
            {
                throw new InvalidCastException("String is in invalid format", ex);
            }
        }

        /// <summary>
        /// Implicit cast from DependencyId to string. 
        /// </summary>
        public static implicit operator String(InstanceId instanceId)
        {
            if (instanceId == null)
                return null;
            return instanceId.ToString();
        }

        /// <summary>
        /// Implicit cast from InstanceIdAttribute to DependencyId. 
        /// </summary>
        public static implicit operator InstanceId(InstanceIdAttribute instanceId)
        {
            try
            {
                if (instanceId == null)
                    return null;
                return InstanceId.Parse(instanceId.Name + SEPARATOR + instanceId.PublicInterface);
            }
            catch (FormatException ex)
            {
                throw new InvalidCastException("InstanceIdAttribute has a invalid Name or PublicInterface.", ex);
            }
        }

        #endregion

        #region Static members

        static InstanceId()
        {
            Null = new InstanceId();
        }

        /// <summary>
        /// The Null Id. defines a nonexistant id.
        /// </summary>
        public static readonly InstanceId Null;
        /// <summary>
        /// Parses a dependency id for a string.
        /// </summary>
        public static InstanceId Parse(string id)
        {
            try
            {
                Ensure.NotNullOrEmpty(id, "id");

                if (id.IndexOf(SEPARATOR) > -1)
                {
                    string[] parts;
                    parts = id.Split(SEPARATOR.ToCharArray());
                    if (parts.Length != 2)
                    {
                        throw new FormatException("id is in the wrong format");
                    }
                    Type depType = Types.GetType(parts[1], true);
                    return new InstanceId(depType, parts[0]);
                }
                else
                {
                    if (id == EMPTY_ID)
                        return InstanceId.Null;
                    return new InstanceId(Type.GetType(id));
                }
            }
            catch (Exception ex)
            {
                throw new FormatException("Parse Error", ex);
            }
        }

        #endregion

    }
}
